// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>

#include <boost/functional/hash.hpp>

#include "coin2/exchange/base/symbology/file_monitor.h"
#include "coin2/exchange/base/symbology/product_info.h"

namespace std {

template <typename... T>
struct hash<tuple<T...>> {
  size_t operator()(tuple<T...> const& arg) const noexcept { return boost::hash_value(arg); }
};

}  // namespace std

namespace coin2::exchange::base::symbology {

class ProductInfoConstantsManager;

/*
 * Search policies:
 *    1. Exact: lookup from product info file on the exact given date.
 *    2. Latest: ignore given date, and lookup in the latest product info file,
 *               if not found, lookup in the product info of of the previous dates.
 *    3. FallbackToPrevious: lookup in the product info of the given date,
 *               if not found, lookup in the product info of of the previous dates.
 *
 *  Search path: if coin_product_info repo is not found in the given path, will use
 *               data/coin2/product_info instead. This is useful during the process
 *               of migration from using the old GetProductHolder to
 *               using the new ProductHolderManager.
 */
class ProductHolderManager {
 public:
  explicit ProductHolderManager(const coin::proto::ProductHolderManagerConfig& config);
  const IProductHolder* GetProductHolder(const IProduct& product, int64_t timestamp);
  ProductInfoConstantsManager* GetMutableProductInfoConstantsManager() {
    return product_info_constants_.get();
  }

  void SetFallBackNDay(uint32_t day) { config_.set_fallback_day(day); }
  void SetHotUpdateStatus(bool enable);

 private:
  bool LoadProductHolder(coin::proto::MarketType, coin::proto::ExchangeType, int64_t, int64_t);
  bool JsonNeedLoad(coin::proto::MarketType, coin::proto::ExchangeType, int64_t);
  const IProductHolder* GetProductHolderAux(
      const IProduct& product, int64_t timestamp, int64_t midnight_ts, int);

 private:
  coin::proto::ProductHolderManagerConfig config_;
  std::unique_ptr<ProductInfoConstantsManager> product_info_constants_;
  std::unordered_set<std::tuple<coin::proto::MarketType, coin::proto::ExchangeType, int64_t>>
      json_loaded_;

  std::unordered_map<
      std::tuple<coin::proto::MarketType, coin::proto::ExchangeType, int64_t, std::string>,
      std::unique_ptr<IProductHolder>>
      product_holders_cache_;
};

class ProductInfoConstantsManager {
 public:
  explicit ProductInfoConstantsManager(const std::vector<std::string>& search_paths);

  const ProductInfoConstant* GetProductInfoConstant(
      coin::proto::MarketType market,
      coin::proto::ExchangeType exchange,
      int64_t ts);

  void SetHotUpdateStatus(bool enable) { hot_update_enabled = enable; }
  bool IsPiConstantUpdated(coin::proto::MarketType, coin::proto::ExchangeType, int64_t) const;

 private:
  bool LoadProductInfoFromPath(
      coin::proto::MarketType, coin::proto::ExchangeType, const std::string& path, int64_t);
  bool LoadProductInfo(coin::proto::MarketType, coin::proto::ExchangeType, int64_t);

 private:
  std::unique_ptr<ProductInfoConstant> GetAdditionalProductInfo(const std::string me);
  using Key = std::tuple<coin::proto::MarketType, coin::proto::ExchangeType, int64_t>;
  std::unordered_map<Key, std::list<std::string>> pi_files_cache_;
  std::unique_ptr<FileMonitor> files_monitor_;
  std::vector<std::string> search_path_list_;
  std::unordered_map<Key, std::unique_ptr<ProductInfoConstant>> pi_constant_cache_;
  bool hot_update_enabled{false};
};  // class ProductInfoConstants

}  // namespace coin2::exchange::base::symbology
